/*************************************************************************
 *                                                                       *
 * This file is part of the FreeMAPs distribution.  For full copyright   *
 * information, see COPYRIGHT and COPYING.LESSER.                        *
 *                                                                       *
 * Author:           sqbang                                              *
 * Email:            sqbang@ustc.edu                                     *
 * Date:             2013.11.21                                          *
 *                                                                       *
 *************************************************************************/

//declaration header
#include "COMMON/info/UnstructuredMeshInfo.h"

//COMMON headers
#include "COMMON/info/InfoManager.h"
#include "COMMON/info/CommonInfo.h"
#include "COMMON/mesh/MeshReader.h"
//#include "COMMON/mesh/MeshGeneratorStrategy.h"
//#include "COMMON/mesh/MeshGroup.h"
#include "COMMON/tbox/LogMessage.h"
#include "COMMON/tbox/MathUtilities.h"

#include <iostream>


namespace FreeMAPs
{

UnstructuredMeshInfo::UnstructuredMeshInfo(
    const std::string& parent_name,
    const std::string& object_name):
    Parent                                            (parent_name, object_name),
    d_build_mesh_from_file                            (true),
    d_mesh_reader                                     (NULL)
    //d_mesh_translation                                (0),
   // d_mesh_rotation                                   (0),
   // d_mesh_scale                                      (1, 1, 1)
{
    d_mesh_reader = new MeshReader();
#if 0
    d_mesh_modification_order.clear();
    d_mesh_modification_order.push_back(GeomTransferOperator::TRANSLATION);
    d_mesh_modification_order.push_back(GeomTransferOperator::ROTATION);
    d_mesh_modification_order.push_back(GeomTransferOperator::SCALE);
#endif
}



UnstructuredMeshInfo::~UnstructuredMeshInfo()
{
    this->clear();
}



void
UnstructuredMeshInfo::clear()
{
//    Utilities::clear_pointer_object (d_mesh_generator);
    Utilities::clear_pointer_object (d_mesh_reader);
    //d_mesh_modification_order.clear();
}



void
UnstructuredMeshInfo::set_mesh_file_format(
        const MeshFileFormat mesh_file_format)
{
    if(!d_mesh_reader)
    {
        d_mesh_reader = new MeshReader();
    }
    d_mesh_reader->set_mesh_file_format(mesh_file_format);
}



void
UnstructuredMeshInfo::set_mesh_file_location(
        const std::string mesh_file_location)
{
    if(!d_mesh_reader)
    {
        d_mesh_reader = new MeshReader();
    }
    d_mesh_reader->set_mesh_file_location(mesh_file_location);
}



const std::string
UnstructuredMeshInfo::get_mesh_file_location() const
{
    std::string file_location;
    if(d_mesh_reader)
    {
        file_location = d_mesh_reader->get_mesh_file_location();
    }
    return file_location;
}



bool
UnstructuredMeshInfo::check_all_info_db (
        const InputDatabase* db)
{
    if( db->key_exists("mesh_reader") )
    {
        bool check_ok = d_mesh_reader->check_all_info_db(db->get_database("mesh_reader"));
        if( !check_ok )
        {
            return false;
        }
    }
    else
    {
        FREEMAPS_ERROR("in \"unstructured_mesh_info\", can not find  \"mesh_reader\" \n");
        return false;
    }

    //mesh_modification_order
    if( db->key_exists("mesh_modification_order") && !db->is_string("mesh_modification_order") )
    {
        FREEMAPS_ERROR("in \"unstructured_mesh_info\", \"mesh_modification_order\" should be string \n");
        return false;
    }

    //mesh_translation
    if( db->key_exists("mesh_translation") && !db->is_double("mesh_translation") )
    {
        FREEMAPS_ERROR("in \"unstructured_mesh_info\", \"mesh_translation\" should be double, like this--> 13.6, 1.2, 0 \n");
        return false;
    }

    //mesh_scale
    if( db->key_exists("mesh_scale") && !db->is_double("mesh_scale") )
    {
        FREEMAPS_ERROR("in \"unstructured_mesh_info\", \"mesh_scale\" should be double, like this--> 200, 200, 200 \n");
        return false;
    }

    //mesh_rotation
    if( db->key_exists("mesh_rotation") && !db->is_double("mesh_rotation") )
    {
        FREEMAPS_ERROR("in \"unstructured_mesh_info\", \"mesh_rotation\" should be double, like this-->  \n");
        return false;
    }

    return true;
}



void
UnstructuredMeshInfo::get_from_input (
        const InputDatabase* db)
{
#if 0
    this->prepare_for_use (db);

    db->insert_allowable_key ("mesh_reader");
    db->insert_allowable_key ("mesh_generator");
    db->insert_allowable_key ("use_parallel_mesh");
    db->insert_allowable_key ("change_all_elems_to_triangle");
    db->insert_allowable_key ("change_elems_order");
    db->insert_allowable_key ("mesh_modification_order");
    db->insert_allowable_key ("mesh_translation");
    db->insert_allowable_key ("mesh_rotation");
    db->insert_allowable_key ("mesh_scale");
    db->insert_allowable_key ("remove_duplicate_nodes");
    db->insert_allowable_key ("remove_duplicate_nodes_using_relative_tolerance");
    db->insert_allowable_key ("remove_duplicate_nodes_tolerance");

    db->check_allowable_keys ();

    int dim = InfoManager::get_instance()->get_common_info()->get_dim();
    //maybe dim have error
    if( dim > 3 || dim == -1)
    {
        dim = 3;
    }

    /*
     * build build_mesh_from_file
     */
    {
        db->check_either_or_keys ("mesh_reader", "mesh_generator");

        if (db->key_exists ("mesh_reader") && db->is_database("mesh_reader"))
        {
            db->check_requisite_key ("mesh_reader", DataType::DATABASE, false, false);
            d_mesh_reader->get_from_input (db->get_database ("mesh_reader"));
        }

    }

    /*
     * build mesh_modification_order, include mesh_translation, mesh_rotation, mesh_scale
     */
    {
        d_mesh_modification_order.clear();
        bool do_translation = false;
        bool do_rotation    = false;
        bool do_scale       = false;
        if (db->key_exists ("mesh_translation") && db->is_double("mesh_translation"))
        {
            db->check_optional_key ("mesh_translation", DataType::DOUBLE, false, true);

            do_translation = true;
            d_mesh_translation.get_from_input (db, "mesh_translation");

            for (int i = 0; i < dim; ++i)
            {
                if (std::abs (d_mesh_translation(i)) > MathUtilities<double>::get_max() / 2.)
                {
                    std::ostringstream os;
                    os << "mesh_translation = " << d_mesh_translation(i) << '\n';
                    os << "it is too big, you should check it." << '\n';
                    LogMessage::unknown_error(
                        db,
                        "mesh_translation",
                        os.str());
                }
            }
        }

        if (db->key_exists ("mesh_rotation") && db->is_double("mesh_rotation"))
        {
            db->check_optional_key ("mesh_rotation", DataType::DOUBLE, false, true);
            do_rotation = true;
            d_mesh_rotation.get_from_input (db, "mesh_rotation");
        }

        if (db->key_exists ("mesh_scale") && db->is_double("mesh_scale"))
        {
            db->check_optional_key ("mesh_scale", DataType::DOUBLE, false, true);
            do_scale = true;
            d_mesh_scale.get_from_input (db, "mesh_scale");

            for (int i = 0; i < dim; ++i)
            {
                if (std::abs (d_mesh_scale(i)) > MathUtilities<double>::get_max() / 2.)
                {
                    std::ostringstream os;
                    os << "mesh_scale = " << d_mesh_scale(i) << '\n';
                    os << "it is too big, you should check it." << '\n';
                    LogMessage::use_improper_double_value(
                        db,
                        "mesh_scale",
                        i,
                        os.str());
                }

                if (std::abs (d_mesh_scale(i)) < MathUtilities<double>::get_min() * 2.)
                {
                    std::ostringstream os;
                    os << "mesh_scale = " << d_mesh_scale(i) << '\n';
                    os << "it is too small, you should check it." << '\n';

                    LogMessage::use_improper_double_value(
                        db,
                        "mesh_scale",
                        i,
                        os.str());
                }
            }
        }


        std::set<GeomTransferOperator> used_trans_ops;
        if (db->key_exists ("mesh_modification_order") && db->is_string("mesh_modification_order"))
        {
            db->check_optional_key ("mesh_modification_order", DataType::STRING, true, true);
            db->check_duplicated_string_value ("mesh_modification_order");

            std::vector<std::string> modi_order = db->get_string_vector("mesh_modification_order");

            for (std::size_t i = 0; i < modi_order.size(); ++i)
            {
                GeomTransferOperator trans_op =
                    Utilities::string_to_enum<GeomTransferOperator>(modi_order[i]);
                if (Utilities::is_invalid_value (trans_op))
                {
                    LogMessage::unrecognized_enum_item<GeomTransferOperator>(
                        db,
                        "mesh_modification_order",
                        static_cast<int>(i));
                }
                else
                {
                    d_mesh_modification_order.push_back (trans_op);
                    used_trans_ops.insert (trans_op);
                }
            }
#if 0
            if (do_translation && used_trans_ops.count (GeomTransferOperator::TRANSLATION) == 0)
            {
                LogMessage::unknown_error(
                    db,
                    "mesh_modification_order",
                    "\"mesh_translation\" has been set while \"mesh_modification_order\" does not refer to it. \n");
            }

            if (!do_translation && used_trans_ops.count (GeomTransferOperator::TRANSLATION) > 0)
            {
                LogMessage::unknown_error(
                    db,
                    "mesh_modification_order",
                    "\"mesh_translation\" has NOT been set while \"mesh_modification_order\" refer to it. \n");
            }

            if (do_rotation && used_trans_ops.count (GeomTransferOperator::ROTATION) == 0)
            {
                LogMessage::unknown_error(
                    db,
                    "mesh_modification_order",
                    "\"mesh_rotation\" has been set while \"mesh_modification_order\" does not refer to it. \n");
            }

            if (!do_rotation && used_trans_ops.count (GeomTransferOperator::ROTATION) > 0)
            {
                LogMessage::unknown_error(
                    db,
                    "mesh_modification_order",
                    "\"mesh_rotation\" has NOT been set while \"mesh_modification_order\" refer to it. \n");
            }

            if (do_scale && used_trans_ops.count (GeomTransferOperator::SCALE) == 0)
            {
                LogMessage::unknown_error(
                    db,
                    "mesh_modification_order",
                    "\"mesh_scale\" has been set while \"mesh_modification_order\" does not refer to it. \n");
            }

            if (!do_scale && used_trans_ops.count (GeomTransferOperator::SCALE) > 0)
            {
                LogMessage::unknown_error(
                    db,
                    "mesh_modification_order",
                    "\"mesh_scale\" has NOT been set while \"mesh_modification_order\" refer to it. \n");
            }
#endif
        } //db->key_exists ("mesh_modification_order")
        else
        {
            if (do_translation)
            {
                d_mesh_modification_order.push_back (GeomTransferOperator::TRANSLATION);
            }

            if (do_rotation)
            {
                d_mesh_modification_order.push_back (GeomTransferOperator::ROTATION);
            }

            if (do_scale)
            {
                d_mesh_modification_order.push_back (GeomTransferOperator::SCALE);
            }
        }
    }
#endif

}



std::ostream&
operator << (

    std::ostream& os,
    const UnstructuredMeshInfo& rhs)
{
 #if 0
    os << "unstructured_mesh_info" << RTN_FLAG;
    os << "{" << RTN_FLAG;

    std::ostringstream oss;

    FREEMAPS_ASSERT_VALID_POINTER (rhs.d_mesh_reader);
    oss << *rhs.d_mesh_reader;
    os << LogMessage::get_log_info (oss.str(), ONE_TAB) << RTN_FLAG << RTN_FLAG;

    if (rhs.d_mesh_modification_order.size() > 0)
    {
        os << LogMessage::get_prefix (ONE_TAB) << "mesh_modification_order = " << Utilities::get_rearranged_enum_vector<GeomTransferOperator>(rhs.d_mesh_modification_order, true, 3) << RTN_FLAG;
        for (unsigned int i = 0; i < rhs.d_mesh_modification_order.size(); ++i)
        {
            if (rhs.d_mesh_modification_order[i] == GeomTransferOperator::TRANSLATION)
            {
                os << LogMessage::get_prefix (ONE_TAB) << "mesh_translation = "<< rhs.d_mesh_translation << RTN_FLAG;
            }

            if (rhs.d_mesh_modification_order[i] == GeomTransferOperator::ROTATION)
            {
                os << LogMessage::get_prefix (ONE_TAB) << "mesh_rotation    = "<< rhs.d_mesh_rotation << RTN_FLAG;
            }

            if (rhs.d_mesh_modification_order[i] == GeomTransferOperator::SCALE)
            {
                os << LogMessage::get_prefix (ONE_TAB) << "mesh_scale       = "<< rhs.d_mesh_scale << RTN_FLAG;
            }
        }
    }

    os << "}";

    return os;
    #endif
}

}
